#pragma once

#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include <utility>
#include <type_traits>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <fstream>
#include <mutex>
#include <memory>
#include <jsoncpp/json/json.h>

#include "log.hpp"
extern Log log;

class AccountUnit
{
private:
    typedef AccountUnit self;
    static const std::string account_path;
public:
    enum MODIFY
    {
        MODIFY_PASSWORD,
        MODIFY_MONEY,
        NUM
    };
public:
    template<typename T1, typename T2, 
            typename = std::enable_if<std::is_convertible<T1, std::string>::value && std::is_convertible<T2, std::string>::value>>
    AccountUnit(T1&& id, T2&& password)
        : _user_id(std::forward<T1>(id))
        , _user_password(std::forward<T2>(password))
    {}
public:
    static self* open(const std::string& user_id)
    {
        self* unit_ptr = new self(user_id, "");
        std::ifstream in_file(self::account_path + user_id + "/message.txt", std::ios::binary | std::ios::ate);
        if (in_file.is_open())
        {
            const auto len = in_file.tellg();
            if (len <= 0)
            {
                delete unit_ptr;
                return nullptr;
            }

            const auto file_size = static_cast<std::size_t>(len);
            in_file.seekg(0, std::ios::beg);

            std::string content;
            content.resize(file_size);
            
            if (!in_file.read(&content[0], file_size))
            {
                delete unit_ptr;
                return nullptr;
            }
            
            Json::Value root;
            Json::Reader().parse(content, root);

            if (root.isMember("password") && root["password"].isString())
                unit_ptr->_user_password = root["password"].asString();
            if (root.isMember("money") && root["money"].isInt())
                unit_ptr->_money = root["money"].asInt();
            if (root.isMember("charge_list") && root["charge_list"].isArray())
            {
                const Json::Value& devicesArray = root["charge_list"];
                for (Json::ArrayIndex i = 0; i < devicesArray.size(); ++i)
                {
                    const Json::Value& device = devicesArray[i];
                    std::string deviceId = device["deviceId"].asString();
                    int battery = device["battery"].asInt();
                    unit_ptr->_charge_list[deviceId] = battery;
                }
            }
#ifdef __TEST_DEBUG__
            log(Log::Debug, "json decode success, user password: %s, user money: %d", unit_ptr->_user_password.c_str(), unit_ptr->_money);
#endif
            in_file.close();
        }
        if (unit_ptr->_user_password.empty())
        {
            delete unit_ptr;
            return nullptr;
        }
        return unit_ptr;
    }
public:
    const std::string _user_id;
    std::string _user_password;
    int _money = 0;
    std::map<std::string, int> _charge_list;
};

class Account
{
private:
    const std::string manage_path = "./account/manage.txt";
private:
    typedef Account self;
    typedef AccountUnit unit_t;
private:
    Account(const self&) = delete;
    self& operator=(const self&) = delete;
private:
    Account()
    {
        std::ifstream in_file(manage_path);
        if (in_file.is_open())
        {
            std::string line;
            while (std::getline(in_file, line))
                _account_map.insert(line);
            in_file.close();
            log(Log::Info, "get account message success");
        }
        else
        {
            log(Log::Fatal, "can't open manage.txt, process exit, manage_path: %s, errstring: %s", manage_path.c_str(), strerror(errno));
            exit(1);
        }
    }
public:
    static Account* get_instance()
	{
		if (instance_ptr == nullptr)
		{
			std::lock_guard<std::mutex> lg(_mtx);
			if (instance_ptr == nullptr)
			{
            	instance_ptr = new Account;
                log(Log::Info, "create instance success");
            }
		}
		return instance_ptr;
	}
private:
    bool create_user_directory(const std::string& user_id)
    {
        std::string dir_path = "./account/" + user_id;
        
        if (mkdir(dir_path.c_str(), 0777) != 0)
        {
            log(Log::Warning, "can't make user dir, dir_path: %s, errstring: %s", dir_path.c_str(), strerror(errno));
            return false;
        }
        return true;
    }
    bool delete_user_directory(const std::string& user_id)
    {
        std::string dir_path = "./account/" + user_id;
        
        DIR* dir = opendir(dir_path.c_str());
        if (!dir)
        {
            log(Log::Warning, "can't open user dir, dir_path: %s, errstring: %s", dir_path.c_str(), errno, strerror(errno));
            return false;
        }
        
        struct dirent* entry;
        while ((entry = readdir(dir)) != nullptr)
        {
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            
            std::string file_path = dir_path + "/" + entry->d_name;
            struct stat statbuf;
            
            if (stat(file_path.c_str(), &statbuf) == -1)
            {
                log(Log::Warning, "can't get file stat , file_path: %s, errstring: %s", file_path.c_str(), strerror(errno));
                continue;
            }
            
            if (S_ISDIR(statbuf.st_mode))
            {
                if (!delete_user_directory(user_id + "/" + entry->d_name))
                {
                    closedir(dir);
                    return false;
                }
            }
            else if (S_ISREG(statbuf.st_mode))
            {
                if (unlink(file_path.c_str()) == -1)
                {
                    log(Log::Warning, "can't delete user file, file_path: %s, errstring: %s", file_path.c_str(), strerror(errno));
                    closedir(dir);
                    return false;
                }
            }
        }
        closedir(dir);
        if (rmdir(dir_path.c_str()) == -1)
        {
            log(Log::Warning, "can't delete user dir, file_path: %s, errstring: %s", dir_path.c_str(), strerror(errno));
            return false;
        }
        return true;
    }
    bool write_user_file(const unit_t& user)
    {
        std::string file_path = "./account/" + user._user_id + "/message.txt";

        std::ofstream file(file_path);
        if (!file.is_open())
        {
            log(Log::Warning, "can't make user message.txt, user_id: %s, errstring: %s", user._user_id.c_str(), strerror(errno));
            return false;
        }

        Json::Value root;
        root["password"] = user._user_password;
        root["money"] = user._money;

        Json::Value charge_list(Json::arrayValue);
        for (auto e : user._charge_list)
        {
            Json::Value device;
            device["deviceId"] = e.first;
            device["battery"] = e.second;
            charge_list.append(std::move(device));
        }
        root["charge_list"] = charge_list;

        file << Json::FastWriter().write(root) << std::endl;
        file.close();
        return true;
    }
    bool delete_user_file(const std::string& user_id)
    {
        std::string file_path = "./account/" + user_id + "/message.txt";
        
        struct stat statbuf;
        if (stat(file_path.c_str(), &statbuf) == -1)
        {
            if (errno == ENOENT)
                return true;
            return false;
        }
        
        if (unlink(file_path.c_str()) == -1)
        {
            log(Log::Warning, "delete file faulty, user_id: %s, errstring: %s", user_id.c_str(), strerror(errno));
            return false;
        }
        return true;
    }
    bool update_manage_file(const std::string& user_id)
    {
        std::ofstream file(manage_path, std::ios::app);
        if (!file.is_open())
        {
            log(Log::Warning, "can't open manage.txt, user_id: %s, errstring: %s", user_id.c_str(), strerror(errno));
            return false;
        }
        file << user_id << std::endl;
        file.close();
        return true;
    }
    bool remove_manage_file(const std::string& user_id)
    {
        std::set<std::string> users;
        
        std::ifstream in_file(manage_path);
        if (in_file.is_open())
        {
            std::string line;
            while (std::getline(in_file, line))
                users.insert(line);
            in_file.close();
        }
        
        std::set<std::string>::iterator it = users.find(user_id);
        if (it != users.end())
            return true;
        else
            users.erase(user_id);
        
        std::ofstream out_file(manage_path);
        if (!out_file.is_open())
        {
            log(Log::Warning, "can't write manage.txt, manage_path: %s, errstring: %s", manage_path.c_str(), strerror(errno));
            return false;
        }
        for (const auto& user : users)
            out_file << user << std::endl;
        out_file.close();
        return true;
    }
public:
	template <class... Args>
    bool emplace_user(Args&&... args)	
    {
        unit_t* unit_ptr = new unit_t(args...);
        if (find_user(unit_ptr->_user_id) == nullptr)
        {
            if (create_user_directory(unit_ptr->_user_id))
            {
                if (write_user_file(*unit_ptr))
                {
                    if (update_manage_file(unit_ptr->_user_id))
                    {
                        _account_map.insert(std::move(unit_ptr->_user_id));
                        log(Log::Info, "insert success, user_id: %s", unit_ptr->_user_id.c_str());
                        return true;
                    }
                }
                delete_user_directory(unit_ptr->_user_id);
            }
            return false;
        }
        else
            false;
    }
    bool insert_user(const unit_t& req)
    {
        return emplace_user(req);
    }
    bool insert_user(unit_t&& req)
    {
        return emplace_user(std::forward<unit_t>(req));
    }

    const std::string* find_user(const std::string& id)
    {
        std::set<std::string>::iterator it = _account_map.find(id);
        if (it != _account_map.end())
        {
#ifdef __TEST_DEBUG__
            log(Log::Debug, "found user, user_id: %s", id.c_str());
#endif
            return &(*it);
        }
        else
        {
#ifdef __TEST_DEBUG__
            log(Log::Debug, "not user, user_id: %s", id.c_str());
#endif
            return nullptr;
        }
    }
    const std::string* find_user(const unit_t& req)
    {
        return find_user(req._user_id);
    }

    bool login_user(const std::string& id, const std::string& password)
    {
        const std::string* ptr = find_user(id);
        if (ptr == nullptr)
            return false;
        std::unique_ptr<unit_t> unit_ptr(unit_t::open(*ptr));
        if (unit_ptr != nullptr && unit_ptr->_user_id == id && unit_ptr->_user_password == password)
            return true;
        return false;
    }
    bool login_user(const unit_t& req)
    {
        return login_user(req._user_id, req._user_password);
    }

    bool modify_user(const std::string& user_id, unit_t::MODIFY type, const std::string& message)
    {
        if (find_user(user_id) == nullptr)
            return false;
        std::unique_ptr<unit_t> unit_ptr(unit_t::open(user_id));
        if (unit_ptr == nullptr)
            return false;
        switch (type)
        {
        case unit_t::MODIFY_PASSWORD :
            unit_ptr->_user_password = message;
            break;
        case unit_t::MODIFY_MONEY :
            unit_ptr->_money += std::stoi(message);
            break;
        default:
            break;
        }
        write_user_file(*unit_ptr);
        log(Log::Info, "modify success, user_id: %s", user_id.c_str());
        return true;
    }
    bool modify_user(const unit_t& req, unit_t::MODIFY type, const std::string& message)
    {
        return modify_user(req._user_id, type, message);
    }

    bool save_user(const unit_t& user)
    {
        return write_user_file(user);
    }

    bool delete_user(const std::string& user_id)
    {
        _account_map.erase(user_id);
        delete_user_directory(user_id);
        remove_manage_file(user_id);
        log(Log::Info, "delete user success, user_id: %s", user_id.c_str());
        return true;
    }
    bool delete_user(const unit_t& req)
    {
        return delete_user(req._user_id);
    }
private:
    std::set<std::string> _account_map;
    static Account* instance_ptr;
	static std::mutex _mtx;
};
